﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using log4net;
using Sherwood.Configuration;

namespace Sherwood.Content.UpdateScheduler
{
    public class Scheduler
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(Scheduler));

        private readonly IConfigRepository _configRepository;
        private readonly string _configurationFileName;
        private readonly configuration _config;
        private Timer _timer;

        public Scheduler(IConfigRepository configRepository)
            : this(configRepository, "UpdateScheduler.xml")
        {
        }

        public Scheduler(IConfigRepository configRepository, string configurationFileName)
        {
            _configRepository = configRepository;
            _configurationFileName = configurationFileName;
            using (var textReader = configRepository.CreateTextReader(configurationFileName))
                _config = configuration.Load(textReader);

            if (Log.IsInfoEnabled)
                Log.Info("Scheduler instance created");
        }

        public void Start(TimeSpan dueTime, TimeSpan checkEvery)
        {
            if (_timer == null)
            {
                _timer = new Timer(Callback, null, dueTime, checkEvery);

                if (Log.IsInfoEnabled)
                    Log.Info("Scheduler started");
            }
        }

        public void Stop()
        {
            if (_timer != null)
            {
                _timer.Dispose();
                _timer = null;

                if (Log.IsInfoEnabled)
                    Log.Info("Scheduler stopped");
            }
        }

        private void Callback(object state)
        {
            if (Monitor.TryEnter(_timer))
            {
                try
                {
                    RunNow();
                }
                finally
                {
                    Monitor.Exit(_timer);
                }
            }
        }

        public void RunNow()
        {
            if (Log.IsDebugEnabled)
                Log.Debug("Entering method RunNow");
            try
            {
                var configuration = _config;
                var context = new UpdateContext();
                context.HandleException += OnHandleRecordException;
                foreach (var run in configuration.schedule.run)
                {
                    if (!run.inactive.GetValueOrDefault() && run.at < DateTime.UtcNow)
                    {
                        if (Log.IsInfoEnabled)
                            Log.InfoFormat("Running {0} sync for client {1}", run.mode, run.client);

                        var client = configuration.clients.client.First(c => c.id == run.client);
                        try
                        {
                            RunJob(context, run, client);
                        }
                        catch (Exception ex)
                        {
                            Log.Error("Failed to execute " + run.mode + " sync for client " + run.client, ex);
                        }
                        if (!string.IsNullOrEmpty(run.repeatEvery))
                        {
                            var timeSpan = TimeSpan.Parse(run.repeatEvery);
                            if (timeSpan > TimeSpan.Zero)
                            {
                                while (run.at <= DateTime.UtcNow)
                                    run.at += timeSpan;
                            }
                            else
                                run.inactive = true;
                        }
                        else
                            run.inactive = true;
                        if (run.inactive.GetValueOrDefault() && Log.IsInfoEnabled)
                            Log.InfoFormat("Set {0} sync for client {1} scheduled to {2} to inactive", run.mode,
                                           run.client, run.at);

                        if (Log.IsDebugEnabled)
                            Log.Debug("Saving configuration");
                        using (var textWriter = _configRepository.CreateTextWriter(_configurationFileName))
                            configuration.Save(textWriter);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error("RunNow failed", ex);
            }
            finally
            {
                if (Log.IsDebugEnabled)
                    Log.Debug("Leaving method RunNow");
            }
        }

        private static void RunJob(UpdateContext context, jobSchedule run, client client)
        {
            var startDate = DateTime.UtcNow;
            switch (run.mode)
            {
                case "Full":
                    RecordPuller.UpdateAll(context, client);
                    break;
                case "Incremental":
                    RecordPuller.UpdateModifiedSince(context, client, client.lastSync);
                    break;
                case "PullDeletions":
                    RecordPuller.PullDeletions(context, client);
                    break;
            }
            client.lastSync = startDate;
        }

        private static void OnHandleRecordException(object sender, UpdateExceptionEventArgs args)
        {
            Log.Error("Failed to update record " + args.ReferenceId, args.Exception);
        }
    }
}